-module(storage_supervisor).
-include("definitions.hrl").
-export([start/0, stop/0, flush/0, get_current_states/0, get_last_for_device/1, clear/0, insert_samples/0, update_state/2, shift/0, init/0, event_loop/4]).


-spec start/0 :: () -> true.
start() ->
  ok = storage:start(),
  {Pid, _} = spawn_monitor(storage_supervisor, init, []),
  true = register(stateStorageSupervisor, Pid).

-spec init/0 :: () -> ok.
init() ->
  true = state_storage:start(),
  State = works,
  storage_supervisor:event_loop(State, [], 0, sets:new()).

-spec shift/0 :: () -> ok.
shift() ->
  case whereis(stateStorageSupervisor) of
    undefined -> error("stateStorageSupervisor is undefined in shift()");
    Pid ->
      Pid ! shift
  end,
  ok.

-spec update_state/2 :: (state(), pid()) -> ok.
update_state(State, WaiterPid) ->
  Atom = get_atom(),
  stateStorageSupervisor ! {device_id, State#stateRecord.deviceId},
  ok = state_storage:update_state(Atom, State, WaiterPid),
  return_atom(),
  ok.

-spec get_current_states/0 :: () -> [state()].
get_current_states() ->
  _ = get_atom(),
  Lst = state_storage:get_current_states(),
  return_atom(),

  _ = get_atom(),
  DeviceIds = get_devices_ids(),
  return_atom(),
  TooLittle = length(Lst) < sets:size(DeviceIds),
  if
    TooLittle ->
      storage:get_last_state_for_devices(sets:to_list(DeviceIds));
    true ->
      storage:get_last_state_for_devices(sets:to_list(DeviceIds))
%%       Lst
  end.

-spec get_atom/0 :: () -> atom().
get_atom() ->
%%    io:format("~p tries to getatom~n", [self()]),
  case whereis(stateStorageSupervisor) of
    undefined -> error("stateStorageSupervisor is undefined in update_state()");
    Pid ->
      Pid ! {get_addr, self()},
      receive
        {your_state_stor, Atom} when is_atom(Atom) ->
%%            io:format("~p gotatom~n", [self()]),
          Atom

      after 200 ->
        try
          {_, MyName} = erlang:process_info(self(), registered_name),
          io:format("my name is ~p (~p)~n", [MyName, self()])
        of
          A -> exit(A)
        catch error:R ->
          io:format("my name is ~p error ~p~n", [self(), R]),
          exit(dead_worker);

          exit:R ->
            io:format("my name is ~p exit ~p~n", [self(), R]),
            exit(dead_worker);

          A:B ->
            io:format("my name is ~p ~p ~p~n", [self(), A, B]),
            exit(dead_worker)
        end
      end
  end.

-spec return_atom/0 :: () -> return_atom.
return_atom() ->
  case whereis(stateStorageSupervisor) of
    undefined ->
      error("stateStorageSupervisor is undefined in return_atom()");
    Pid ->
%%        io:format("~p returnedatom~n", [self()]),
      Pid ! return_atom
  end.

-spec get_last_for_device/1 :: (device_id()) -> state().
get_last_for_device(DeviceId) ->
  _ = get_atom(),
  State = state_storage:get_last_for_device(DeviceId),
  return_atom(),
  State.

-spec stop/0 :: () -> ok.
stop() ->
  case whereis(stateStorageSupervisor) of
    undefined -> error("stateStorageSupervisor is undefined in stop");
    Pid -> Pid ! {stop, self()},
      receive
        stateStorageSupervisor_stopped ->
          io:format("storage supervisor finished~n")
      end,
      ok = state_storage:stop(),
      ok = storage:stop()
  end.

-spec clear/0 :: () -> ok.
clear() ->
  _ = get_atom(),
  ok = state_storage:clear(),
  return_atom(),
  ok.

-spec insert_samples/0 :: () -> ok.
insert_samples() ->
  case whereis(stateStorageSupervisor) of
    undefined ->
      error("stateStorageSupervisor is undefined in insert_samples~n");
    Pid when is_pid(Pid) -> Pid ! {insert_samples, self()},
      receive
        inserted -> ok
      end
  end
.

%блокирующая
-spec flush/0 :: () -> ok.
flush() ->
  _ = get_atom(),
  ok = state_storage:flush(),
  return_atom(),
  ok.


-spec event_loop/4 :: (works  | reloads, [{get_addr, pid()}], integer(), sets:set(device_id())) -> ok.

event_loop(State, Queue, AtomsSent, DeviceIds) ->
  ShouldSay = (State == reloads) and (AtomsSent == 0),
  if
    ShouldSay ->
      state_storage:try_reboot();
    true ->
      ok
  end,
  receive
    {get_addr, From} when is_pid(From) ->
      case State of
        works ->
          From ! {your_state_stor, state_storage},
          event_loop(State, Queue, AtomsSent + 1, DeviceIds);

        reloads ->
          event_loop(State, Queue ++ [{get_addr, From}], AtomsSent, DeviceIds)
      end;

    return_atom ->
      event_loop(State, Queue, AtomsSent - 1, DeviceIds);

    {'DOWN', Ref, process, _, Reason} ->
      case State of
        works -> error("not now");
        _ ->
          erlang:demonitor(Ref),
          case Reason of
            normal ->
              ok;
            R -> io:format("state_storage fell because of ~p~n", [R])
          end,
          true = state_storage:start(),
          ok = clearQueue(Queue),
          event_loop(works, [], AtomsSent + length(Queue), DeviceIds)
      end;

    shift ->
%%       io:format("shift at ~p~n", [State]),
      case State of
        works ->
          state_storage ! get_ready,
          event_loop(reloads, Queue, AtomsSent, DeviceIds);

        reloads ->
          event_loop(works, Queue, AtomsSent, DeviceIds)
      end;

    {device_id, N} when is_integer(N), N > 0 ->
      case sets:is_element(N, DeviceIds) of
        true ->
          event_loop(State, Queue, AtomsSent, DeviceIds);
        false ->
          event_loop(State, Queue, AtomsSent, sets:add_element(N, DeviceIds))
      end;

    {get_devices_ids, From} when is_pid(From) ->
      From ! {devices_ids, DeviceIds},
      event_loop(State, Queue, AtomsSent, DeviceIds);

    {insert_samples, From} when is_pid(From) ->
      ok = storage:insert_samples(),
      From ! inserted,
      event_loop(State, Queue, AtomsSent, DeviceIds);

    {stop, From} when is_pid(From) ->
      From ! stateStorageSupervisor_stopped,
      ok

%%   after 1000 ->
%%     event_loop(State, Queue, AtomsSent, DeviceIds)
  end.

-spec clearQueue/1 :: (list({get_addr, pid()})) -> ok.
clearQueue(List) when is_list(List) ->
  ok = lists:foreach(fun handle_imp_message/1, List).

handle_imp_message({get_addr, From}) when is_pid(From) ->
  From ! {your_state_stor, state_storage}.


-spec get_devices_ids/0 :: () -> sets:set(device_id()).
get_devices_ids() ->
  case whereis(stateStorageSupervisor) of
    undefined ->
      error("stateStorageSupervisor is undefined in update_state()");
    Pid ->
      Pid ! {get_devices_ids, self()},
      receive
        {devices_ids, SetIds} -> SetIds
      end
  end.